package gates;

import register.Register;
import mathtools.*;
import gates.MatrixGate;
import exceptions.noQubitException;

/**
 * <p>Cnot Gate in Matrix Representation for specific Control and Target Qubits.</p>
 * <p>The Gate is constructed by the addition of two matrices, A & B. Each matrix depends
 * on a combination of an outer product and the 2x2 identity matrix and a 2x2 Not 
 * matrix. The number of operations for each of the matrices is proportional to 
 * the number of Qubits in the Register. The matrix A has a total of (N-1)
 * tensor product operations of the 2x2 Identity matrix, where N is the number
 * of Qubits in the Register, and one operation of the outer product of two
 * one Qubit states. Ie. The outer product of |0><0|. The position of the outer
 * product is dependent on the specified Control bit of the Gate. The matrix B
 * has a total of (N-2) Identity operations with an outer product operation and
 * a Not operation. The outer product for matrix B is |1><1|, and the position
 * is the same as for matrix A. The position of the Not operation is dependent
 * on the specified position of the target Qubit. The Cnot matrix is then the 
 * addition of matrices A and B. </p>
 * <p>For example the Cnot applied to a 4 Qubit Register(Qubits 0,1,2 and 3)
 *  with Control bit 1 and target 2 would become:<br />
 *  A = I x |0><0| x I x I <br />
 *  B = I x |1><1| x Not x I <br />
 *  Cnot = A + B<br />
 *  Where x represents the Tensor Product, A and B are two construction matrices,
 *  I is the 2x2 Identity matrix, |0><0| and |1><1| are the two outer products and 
 *  Not is a 2x2 inversion matrix (0->1 and 1->0).</p>
 * @author Richard Inskip, Jan Zaucha
 *
 */
public class CnotMatrix extends MatrixGate {

	/* I2 - Identity matrix 2x2
	 * Not - 2x2 Not matrix :	|0 1|
	 * 							|1 0|
	 * outer_0 and outer_1 is the Outer Product (tensor product for vectors)
	 * outer_0:	|1 0|
	 * 			|0 0|
	 * outer_1	|0 0|
	 * 			|0 1|
	 * Variables LHS/RHS are two matrices in which we use as running counters to
	 * finally add them together.
	 * num1 is a complex number representation of integer 1
	 * Set ctrl and target to be accessible globally within the Class
	 */
	/** Store the target qubit the gate acts on*/	
	private int target;
	/** Store the control qubit the gate acts on*/	
	private int ctrl;
	/** Store a 2x2 identity matrix */
	private Matrix I2 = Matrix.IdentityMatrix(2);
	/** Stores a 2x2 not gate in matrix form */
	private Matrix Not;
	/** Matrix needed to construct the cnot matrix */
	private Matrix outer_0;
	/** Matrix needed to construct the cnot matrix */
	private Matrix outer_1;
	/** Matrix needed to construct the cnot matrix */
	private Matrix LHS;
	/** Matrix needed to construct the cnot matrix */
	private Matrix RHS;
	/** Complex number 1+0i needed to set elements on the matrices */
	private Complex num1;
	

	/**
	 * Constructor
	 * @param ctrl The Qubit in the Quantum register to act as the Control - decide if the target has to be changed.
	 * @param target The Qubit in the Quantum register to act as the Target - the Qubit that will change from 0 - 1.
	 */
	protected CnotMatrix(int ctrl, int target){

		/* Make sure the ctrl and target are not set to the same bit.
		 * This is not allowed.
		 */
		if(ctrl==target){
			throw new IllegalArgumentException("ERROR: Cnot cannot be applied with equal Control and Target bits.");
		}

		// Set ctrl and target so it can be used in apply.
		this.ctrl = ctrl;
		this.target = target;

		/*
		 * Set all the constant matrices and values to be used within
		 * the Gate. 
		 */
		num1 = new Complex(1.0,0.0);

		outer_0 = new Matrix(2,2,0.0);
		outer_0.setElement(0, 0, num1);
		outer_1 = new Matrix(2,2,0.0);
		outer_1.setElement(1,1,num1);

		Not = new Matrix(2,2,0.0);
		Not.setElement(0,1,num1);
		Not.setElement(1,0,num1);

		/* Check special values of ctrl and target (the first Qubit) and if this is
		 * the case, set the corresponding initial values.
		 * If the ctrl is 0, the outer product must be constructed at the 
		 * beginning of the tensor products for each side of the matrix addition.
		 * If the target is 0, the Not matrix must be at the beginning of the 
		 * RightHandSide tensor product.
		 */
		if (ctrl==0){
			LHS = outer_0;
			RHS = outer_1;
		}
		else if (target==0){
			LHS = I2;
			RHS = Not;
		}
		else{
			LHS = I2;
			RHS = I2;
		}

	}

	/** The apply method which will allow the Gate to be appied to the Register.
	 * @param r The Quantum Register
	 */
	public void apply(Register r) {
		// Find the length of the Quantum Register
		int numQubits = r.getNumQbits(); 

		/* Check to make sure the register is of adequate size for 
		 * the gate to be applied to. If not throw exception
		 */
		if(numQubits<2){
			throw new noQubitException("ERROR: Cnot can only be applied to a Quantum Register with => 2 Quantum Bits.");
		}
		
		if (target >= r.getNumQbits() || target < 0){
			throw new IllegalArgumentException("Target qubit not in register. Register size (qubits): " + r.getNumQbits() + " Target qubit: " + target);
		}
		
		if (ctrl >= r.getNumQbits() || ctrl < 0){
			throw new IllegalArgumentException("Control qubit not in register. Register size (qubits): " + r.getNumQbits() + " Control qubit: " + ctrl);
		}

		/* Since the initial steps have already been set in the Gate constructor
		 * which depend on the specific ctrl and target set, we then loop over the
		 * remaining number of Qubits in the register to either set the position
		 * of our outer products or the position for the Not matrix. The loop
		 * will then build up both the LHS and RHS so they can then be added and 
		 * applied to the register.
		 */
		for (int i=1; i<numQubits;i++){
			if(i==ctrl){
				LHS = LHS.tensorProduct(outer_0);
				RHS = RHS.tensorProduct(outer_1);
			}
			else if(i==target){
				LHS = LHS.tensorProduct(I2);
				RHS = RHS.tensorProduct(Not);
			}
			else{
				LHS = LHS.tensorProduct(I2);
				RHS = RHS.tensorProduct(I2);
			}
		}

		// Add LHS & RHS and apply matrix to the register and update.
		Matrix result = RHS.addMatrix(LHS);	
		r.update(result.multiplyMatrix(r));
	}


	/**
	 *  Return the name of the matrix.
	 */
	public String getName() {
		return "Cnot";
	}

}
